SaveFile.js ➔ onWindowReady   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
const BluePromise = require("bluebird");
2
const fs = require("fs");
3
const electron = require("electron");
4
const { dialog } = electron;
5
const fs2 = BluePromise.promisifyAll(fs);
0 ignored issues
show
Unused Code introduced by
The constant fs2 seems to be never used. Consider removing it.
Loading history...
6
const _ = require("lodash");
0 ignored issues
show
Unused Code introduced by
The constant _ seems to be never used. Consider removing it.
Loading history...
7
8
module.exports = class SaveFile {
9
    constructor(app) {
10
        this.app = app;
11
12
        // Init empty path and data
13
        this.onPathChange();
14
        this.onDataChange();
15
16
        this.app.on("menu-clearRecentDocs", this.clearRecentDocs.bind(this));
17
        this.app.on("menu-openRecentDocs", this.openRecentDocs.bind(this));
18
        this.app.on("menu-open", this.openFile.bind(this));
19
        this.app.on("menu-reopen", this.reOpenFile.bind(this));
20
        this.app.on("menu-new", this.closeFile.bind(this));
21
        this.app.on("menu-save", this.saveFile.bind(this));
22
        this.app.on("menu-saveAs", this.saveAsFile.bind(this));
23
        this.app.on("menu-saveAsCopy", this.saveAsCopyFile.bind(this));
24
        this.app.on("menu-wipe", this.wipeUnusedSpace.bind(this));
25
26
        this.app.on("ipcFrom-dataUpdate", this.onDataChange.bind(this));
27
28
        this.app.on("window-ready", this.onWindowReady.bind(this));
29
    }
30
31
    onWindowReady() {
32
        this.onPathChange(this.filePath);
33
    }
34
35
    onPathChange(path = "") {
36
        if (path !== "")
37
            this.addRecentDocument(path);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
38
39
        this.filePath = path;
40
        this.app.emit("ipcTo", "pathChange", path);
41
    }
42
43
    /**
44
     * This should be the only method to be called to change data
45
     * @param {Uint8Array} data New data to apply
46
     * @param {boolean} fromRender Is this data from the render?
47
     * @param {boolean} internalOnly Should this data silently change?
48
     */
49
    onDataChange(data = new Uint8Array(0x8000), fromRender = false, internalOnly = false) {
50
        this.fileData = data;
51
52
        if (!fromRender)
53
            this.app.emit("ipcTo", "dataChange", data, internalOnly);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
54
55
        if (fromRender && this.pendingSave)
56
            this._writeSaveFile();
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
57
    }
58
59
    // Adds a file to the list of recent documents which is persistent
60
    // Only 10 unique non-duplicate files are kept meaning each entry will be
61
    // a different file, the samefile will remain in the same slot
62
    // They are added to the top, oldest at the bottom
63
    // They are accessible via CommandOrControl+Shift+# <0-9>
64
    addRecentDocument(path) {
65
        let recentDocs = this.app.store.get('recentDocs', []);
66
        recentDocs.unshift(path);
67
        recentDocs = _.uniq(recentDocs);
0 ignored issues
show
Bug introduced by
The variable _ seems to be never declared. If this is a global, consider adding a /** global: _ */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
68
        if (recentDocs.length > 10)
69
            recentDocs.length = 10;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
70
        this.app.store.set('recentDocs', recentDocs);
71
    }
72
73
    openRecentDocs(index) {
74
        let recentDocs = this.app.store.get('recentDocs', []);
75
        this.readSaveFile(recentDocs[index]);
76
    }
77
78
    // Handles Open File Dialog
79
    // We want this to use es6 async/await and since it never throws an error
80
    // we can't use Bluebird so we need to promisfy it manually
81
    openOpenFileDialog(title) {
82
        return new Promise((resolve) => {
83
            dialog.showOpenDialog({
0 ignored issues
show
Bug introduced by
The variable dialog seems to be never declared. If this is a global, consider adding a /** global: dialog */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
84
                title,
85
                buttonLabel: "Open",
86
                filters: [
87
                    { name: 'SAV Files', extensions: ['sav'] },
88
                    { name: 'All Files', extensions: ['*'] },
89
                ],
90
                properties: [
91
                    "openFile",
92
                    "treatPackageAsDirectory",
93
                ],
94
            }, (files) => {
95
                resolve(files);
96
            });
97
        });
98
    }
99
100
    // Handles Save File Dialog
101
    // We want this to use es6 async/await and since it never throws an error
102
    // we can't use Bluebird so we need to promisfy it manually
103
    openSaveFileDialog(title) {
104
        return new Promise((resolve) => {
105
            dialog.showSaveDialog({
0 ignored issues
show
Bug introduced by
The variable dialog seems to be never declared. If this is a global, consider adding a /** global: dialog */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
106
                title,
107
                buttonLabel: "Save",
108
                filters: [
109
                    { name: 'SAV Files', extensions: ['sav'] },
110
                    { name: 'All Files', extensions: ['*'] },
111
                ],
112
            }, (file) => {
113
                resolve(file);
114
            });
115
        });
116
    }
117
118
    // Handles loading file into memory
119
    async readSaveFile(filePath) {
120
        // Read file
121
        const data = await fs2.readFileAsync(filePath);
0 ignored issues
show
Bug introduced by
The variable fs2 seems to be never declared. If this is a global, consider adding a /** global: fs2 */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
122
123
        this.onDataChange(data);
124
        this.onPathChange(filePath);
125
    }
126
127
    // Cache and save requested data update
128
    async _writeSaveFile() {
129
        await fs2.writeFileAsync(this.pendingSave, this.fileData);
0 ignored issues
show
Bug introduced by
The variable fs2 seems to be never declared. If this is a global, consider adding a /** global: fs2 */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
130
        this.pendingSave = null;
131
    }
132
133
    // Request data update
134
    async writeSaveFile(_filePath = this.filePath) {
135
        this.pendingSave = _filePath;
136
        this.app.emit("ipcTo", "dataUpdate");
137
    }
138
139
    clearRecentDocs() {
140
        this.app.store.set('recentDocs', []);
141
    }
142
143
    // Initiates an open file dialog to open save file
144
    async openFile() {
145
        const fileNames = await this.openOpenFileDialog("Open Save File");
146
147
        if (fileNames === undefined) {
148
            return;
149
        }
150
151
        const filePath = fileNames[0];
152
        await this.readSaveFile(filePath);
153
    }
154
155
    // Reloads file from disk erasing unsaved changes, if no open file is
156
    // present just resets buffer
157
    async reOpenFile() {
158
        // If theres no open file then reload an empty array
159
        if (this.filePath === "") {
160
            this.onDataChange();
161
            return;
162
        }
163
164
        await this.readSaveFile(this.filePath);
165
    }
166
167
    // Closes file in memory and erases buffer
168
    closeFile() {
169
        this.onDataChange();
170
        this.onPathChange();
171
    }
172
173
    // Save file
174
    async saveFile() {
175
        if (this.filePath === "") {
176
            await this.saveAsFile();
177
            return;
178
        }
179
180
        await this.writeSaveFile();
181
    }
182
183
    // Save file as...
184
    async saveAsFile() {
185
        const fileName = await this.openSaveFileDialog("Save File As...");
186
187
        if (fileName === undefined) {
188
            return;
189
        }
190
191
        await this.saveFile();
192
        this.onPathChange(fileName);
193
    }
194
195
    // Save copy of file
196
    async saveAsCopyFile() {
197
        const fileName = await this.openSaveFileDialog("Save Copy As...");
198
199
        if (fileName === undefined) {
200
            return;
201
        }
202
203
        await this.writeSaveFile(fileName);
204
    }
205
206
    // This erases the raw internal data completely leaving the file all zeroes
207
    // Normally the expanded copy will overwrite only the used bytes and leave
208
    // everything else as-is however with this method the expanded copy will
209
    // still do the same but will be writing back to a clean slate thus blasting
210
    // away all unused values
211
    wipeUnusedSpace() {
212
        // Mark internal only to be true so that the render doesn't treat it like
213
        // a new file, all existing data is kept
214
        this.onDataChange(undefined, false, true);
215
    }
216
}
217